home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1995 January / macformat-020.iso / Shareware City / Developers / OutOfPhase1.01Source / OutOfPhase Folder / Level 0 Macintosh 07Aug94 / SerialPort.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-10-01  |  16.9 KB  |  561 lines  |  [TEXT/KAHL]

  1. /* SerialPort.c */
  2. /*****************************************************************************/
  3. /*                                                                           */
  4. /*    System Dependency Library for Building Portable Software               */
  5. /*    Macintosh Version                                                      */
  6. /*    Written by Thomas R. Lawrence, 1993 - 1994.                            */
  7. /*                                                                           */
  8. /*    This file is Public Domain; it may be used for any purpose whatsoever  */
  9. /*    without restriction.                                                   */
  10. /*                                                                           */
  11. /*    This package is distributed in the hope that it will be useful,        */
  12. /*    but WITHOUT ANY WARRANTY; without even the implied warranty of         */
  13. /*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                   */
  14. /*                                                                           */
  15. /*    Thomas R. Lawrence can be reached at tomlaw@world.std.com.             */
  16. /*                                                                           */
  17. /*****************************************************************************/
  18.  
  19. #include "MiscInfo.h"
  20. #include "Audit.h"
  21. #include "Debug.h"
  22. #include "Definitions.h"
  23.  
  24. #pragma options(pack_enums)
  25. #include <Serial.h>
  26. #include <Devices.h>
  27. #include <Events.h>
  28. #pragma options(!pack_enums)
  29.  
  30. #include "SerialPort.h"
  31. #include "Memory.h"
  32. #include "EventLoop.h"
  33. #include "Array.h"
  34.  
  35.  
  36. #define DC1 (17) /* XOn character */
  37. #define DC3 (19) /* XOff character */
  38.  
  39. /* this value is the length of time we'll wait for a write cell to drain.  if */
  40. /* it doesn't drain in this much time, we return a timeout error. */
  41. /* time units are in 1/60ths of a second */
  42. #define IOTIMEOUT (6*60)  /* 6 seconds to write a byte to the output buffer */
  43.  
  44. /* this defines the size of the input buffer */
  45. #define INPUTBUFFERSIZE (16384)
  46.  
  47. /* this defines how many bytes a single asynchronous write operation can handle. */
  48. #define WRITECELLSIZE (384)
  49.  
  50. /* this defines how many write cells are allocated */
  51. #define NUMWRITECELLS (16)
  52.  
  53. /* this structure is one write cell buffer, and can be used for one asynchronous */
  54. /* write operation */
  55. typedef struct
  56.     {
  57.         ParamBlockRec            MyPB;
  58.         char                            Buffer[WRITECELLSIZE];
  59.         MyBoolean                    InUseFlag;
  60.         long                            NumBytes;
  61.     } WriteCell;
  62.  
  63.  
  64. struct SerialPortRec
  65.     {
  66.         short                        InputPortRefNum;
  67.         short                        OutputPortRefNum;
  68.         MyBoolean                GracePeriodInEffect;
  69.         char                        InputBuffer[INPUTBUFFERSIZE];
  70.         WriteCell                WriteCellArray[NUMWRITECELLS];
  71.     };
  72.  
  73.  
  74. struct SerialRefRec
  75.     {
  76.         long                        PortIndex;
  77.     };
  78.  
  79.  
  80. EXECUTE(static MyBoolean                Initialized = False;)
  81.  
  82. EXECUTE(static long                            RefCount = 0;)
  83.  
  84. EXECUTE(static ArrayRec*                ListOfRefs;)
  85.  
  86.  
  87. /* prototype for callback routine */
  88. static pascal void    Callback(void);
  89.  
  90.  
  91. /* initialize serial port subsystem.  the user calls this.  this is not called */
  92. /* from the normal Level 0 initialization since this module is optional. */
  93. MyBoolean                        InitializeSerialPorts(void)
  94.     {
  95.         ERROR(Initialized,PRERR(ForceAbort,"InitializeSerialPorts:  already initialized"));
  96.         EXECUTE(RefCount = 0;)
  97. #if DEBUG
  98.         ListOfRefs = NewArray();
  99.         if (ListOfRefs == NIL)
  100.             {
  101.                 return False;
  102.             }
  103. #endif
  104.         EXECUTE(Initialized = True;)
  105.         return True;
  106.     }
  107.  
  108.  
  109. /* shut down serial ports */
  110. void                                ShutdownSerialPorts(void)
  111.     {
  112.         ERROR(!Initialized,PRERR(ForceAbort,"ShutdownSerialPorts:  not initialized"));
  113.         ERROR(RefCount != 0,PRERR(AllowResume,
  114.             "ShutdownSerialPorts:  some connections still open"));
  115. #if DEBUG
  116.         ERROR(ArrayGetLength(ListOfRefs) != 0,PRERR(AllowResume,
  117.             "ShutdownSerialPorts:  some references have not been disposed"));
  118.         DisposeArray(ListOfRefs);
  119. #endif
  120.         EXECUTE(Initialized = False;)
  121.     }
  122.  
  123.  
  124. /* request a port.  if the port can not be allocated, it will return NIL. */
  125. SerialPortRec*            RequestSerialPort(long BitsRate, SerialRefRec* PortIdentifier,
  126.                                             ParityTypes Parity, HandShakeTypes HandShake,
  127.                                             DataBitTypes NumDataBits, StopBitTypes NumStopBits)
  128.     {
  129.         SerialPortRec*        SerialPort;
  130.         long                            Scan;
  131.         OSErr                            Error;
  132.         long                            Config;
  133.         short                            BaudTemp;
  134.         SerShk                        Shaker;
  135.  
  136.         ERROR(!Initialized,PRERR(ForceAbort,"RequestSerialPort:  not initialized"));
  137.         /* create the memory block.  it must never relocate after this. */
  138.         ERROR(ArrayFindElement(ListOfRefs,PortIdentifier) == -1,PRERR(ForceAbort,
  139.             "RequestSerialPort:  invalid reference"));
  140.         CheckPtrExistence(PortIdentifier);
  141.         SerialPort = (SerialPortRec*)AllocPtrCanFail(sizeof(SerialPortRec),"SerialPortRec");
  142.         if (SerialPort == NIL)
  143.             {
  144.              FailurePoint1:
  145.                 return NIL;
  146.             }
  147.         /* open the serial port */
  148.         switch (PortIdentifier->PortIndex)
  149.             {
  150.                 default: /* bad serial port ID number */
  151.                     EXECUTE(PRERR(ForceAbort,"RequestSerialPort:  bad serial port ID number"));
  152.                     break;
  153.                 case 0: /* modem port */
  154.                     Error = OpenDriver("\p.AOut",&(SerialPort->OutputPortRefNum));
  155.                     if (Error != noErr)
  156.                         {
  157.                             ReleasePtr((char*)SerialPort);
  158.                             goto FailurePoint1;
  159.                         }
  160.                     Error = OpenDriver("\p.AIn",&(SerialPort->InputPortRefNum));
  161.                     if (Error != noErr)
  162.                         {
  163.                             CloseDriver(SerialPort->OutputPortRefNum);
  164.                             ReleasePtr((char*)SerialPort);
  165.                             goto FailurePoint1;
  166.                         }
  167.                     break;
  168.                 case 1: /* printer port */
  169.                     Error = OpenDriver("\p.BOut",&(SerialPort->OutputPortRefNum));
  170.                     if (Error != noErr)
  171.                         {
  172.                             ReleasePtr((char*)SerialPort);
  173.                             goto FailurePoint1;
  174.                         }
  175.                     Error = OpenDriver("\p.BIn",&(SerialPort->InputPortRefNum));
  176.                     if (Error != noErr)
  177.                         {
  178.                             CloseDriver(SerialPort->OutputPortRefNum);
  179.                             ReleasePtr((char*)SerialPort);
  180.                             goto FailurePoint1;
  181.                         }
  182.                     break;
  183.             }
  184.         /* clear the in-use fields of the write cells */
  185.         for (Scan = 0; Scan < NUMWRITECELLS; Scan += 1)
  186.             {
  187.                 SerialPort->WriteCellArray[Scan].InUseFlag = False;
  188.             }
  189.         /* initialize the various important parameters */
  190.         Config = 0;
  191.         switch (Parity)
  192.             {
  193.                 case eParityNone:
  194.                     Config |= noParity;
  195.                     break;
  196.                 case eParityEven:
  197.                     Config |= evenParity;
  198.                     break;
  199.                 case eParityOdd:
  200.                     Config |= oddParity;
  201.                     break;
  202.                 default:
  203.                     EXECUTE(PRERR(ForceAbort,"Unsupported parity"));
  204.                     break;
  205.             }
  206.         switch (NumDataBits)
  207.             {
  208.                 case e8DataBits:
  209.                     Config |= data8;
  210.                     break;
  211.                 case e7DataBits:
  212.                     Config |= data7;
  213.                     break;
  214.                 case e6DataBits:
  215.                     Config |= data6;
  216.                     break;
  217.                 case e5DataBits:
  218.                     Config |= data5;
  219.                     break;
  220.                 default:
  221.                     EXECUTE(PRERR(ForceAbort,"Unsupported number of data bits"));
  222.                     break;
  223.             }
  224.         switch (NumStopBits)
  225.             {
  226.                 case eOneStopBit:
  227.                     Config |= stop10;
  228.                     break;
  229.                 case eOneAndAHalfStopBits:
  230.                     Config |= stop15;
  231.                     break;
  232.                 case eTwoStopBits:
  233.                     Config |= stop20;
  234.                     break;
  235.                 default:
  236.                     EXECUTE(PRERR(ForceAbort,"Unsupported number of stop bits"));
  237.                     break;
  238.             }
  239.         SerReset(SerialPort->InputPortRefNum,Config);
  240.         SerReset(SerialPort->OutputPortRefNum,Config);
  241.         /* set the baud rate */
  242.         BaudTemp = GetClosestAvailableBaudRate(BitsRate,PortIdentifier);
  243.         Control(SerialPort->OutputPortRefNum,13,&BaudTemp);
  244.         BaudTemp = GetClosestAvailableBaudRate(BitsRate,PortIdentifier);
  245.         Control(SerialPort->InputPortRefNum,13,&BaudTemp);
  246.         /* setting the buffer */
  247.         SerSetBuf(SerialPort->InputPortRefNum,&(SerialPort->InputBuffer[0]),INPUTBUFFERSIZE);
  248.         /* setting handshake parameters */
  249.         Shaker.xOn = DC1;
  250.         Shaker.xOff = DC3;
  251.         Shaker.fXOn = (HandShake == eHandShakeXonXoff);
  252.         Shaker.fCTS = (HandShake == eHandShakeDtrCts) || (HandShake == eHandShakeCtsOnly);
  253.         Shaker.errs = 0;
  254.         Shaker.evts = 0;
  255.         Shaker.fInX = (HandShake == eHandShakeXonXoff);
  256.         Shaker.fDTR = (HandShake == eHandShakeDtrCts) || (HandShake == eHandShakeDtrOnly);
  257.         SerHShake(SerialPort->InputPortRefNum,&Shaker);
  258.         SerHShake(SerialPort->OutputPortRefNum,&Shaker);
  259.         /* connection now established */
  260.         SerialPort->GracePeriodInEffect = False;
  261.         EXECUTE(RefCount += 1;)
  262.         return SerialPort;
  263.     }
  264.  
  265.  
  266. /* close a port */
  267. void                                CloseSerialPort(SerialPortRec* SerialPort)
  268.     {
  269.         ERROR(!Initialized,PRERR(ForceAbort,"CloseSerialPort:  not initialized"));
  270.         CheckPtrExistence(SerialPort);
  271.         /* make sure no callbacks are waiting to be called */
  272.         KillIO(SerialPort->OutputPortRefNum);
  273.         KillIO(SerialPort->InputPortRefNum);
  274.         /* dump the drivers */
  275.         CloseDriver(SerialPort->InputPortRefNum);
  276.         CloseDriver(SerialPort->OutputPortRefNum);
  277.         /* release the memory */
  278.         ReleasePtr((char*)SerialPort);
  279.         EXECUTE(RefCount -= 1;)
  280.     }
  281.  
  282.  
  283. /* get how many serial ports there are on the system */
  284. long                                GetNumSerialPorts(void)
  285.     {
  286.         ERROR(!Initialized,PRERR(ForceAbort,"GetNumSerialPorts:  not initialized"));
  287.         return 2;
  288.     }
  289.  
  290.  
  291. /* get the ID of a serial port from the list */
  292. SerialRefRec*                GetIndexedSerialPort(long Index)
  293.     {
  294.         SerialRefRec*            Ref;
  295.  
  296.         ERROR(!Initialized,PRERR(ForceAbort,"GetIndexedSerialPort:  not initialized"));
  297.         ERROR((Index < 0) || (Index >= GetNumSerialPorts()),PRERR(ForceAbort,
  298.             "GetIndexedSerialPort:  port index is out of range"));
  299.         Ref = (SerialRefRec*)AllocPtrCanFail(sizeof(SerialRefRec),"SerialRefRec");
  300.         if (Ref == NIL)
  301.             {
  302.              FailurePoint1:
  303.                 return NIL;
  304.             }
  305.         Ref->PortIndex = Index;
  306.         EXECUTE(if (!ArrayAppendElement(ListOfRefs,Ref)) {ReleasePtr((char*)Ref); Ref = NIL;});
  307.         return Ref;
  308.     }
  309.  
  310.  
  311. /* get the name associated with a serial port identifier */
  312. char*                                GetSerialPortName(SerialRefRec* TheIdentifier)
  313.     {
  314.         char*                            Name;
  315.  
  316.         ERROR(!Initialized,PRERR(ForceAbort,"GetSerialPortName:  not initialized"));
  317.         ERROR(ArrayFindElement(ListOfRefs,TheIdentifier) == -1,PRERR(ForceAbort,
  318.             "GetSerialPortName:  invalid reference"));
  319.         CheckPtrExistence(TheIdentifier);
  320.         switch (TheIdentifier->PortIndex)
  321.             {
  322.                 case 0:  /* modem port */
  323.                     Name = AllocPtrCanFail(10,"SerialPortName");
  324.                     if (Name != NIL)
  325.                         {
  326.                             CopyData("Modem Port",Name,10);
  327.                         }
  328.                     break;
  329.                 case 1:  /* printer port */
  330.                     Name = AllocPtrCanFail(12,"SerialPortName");
  331.                     if (Name != NIL)
  332.                         {
  333.                             CopyData("Printer Port",Name,12);
  334.                         }
  335.                     break;
  336.                 default:
  337.                     EXECUTE(PRERR(ForceAbort,"GetSerialPortName:  bad port number"));
  338.                     break;
  339.             }
  340.         return Name;
  341.     }
  342.  
  343.  
  344. /* dispose of a serial port reference */
  345. void                                DisposeSerialRef(SerialRefRec* TheIdentifier)
  346.     {
  347.         CheckPtrExistence(TheIdentifier);
  348.         ERROR(ArrayFindElement(ListOfRefs,TheIdentifier) == -1,PRERR(ForceAbort,
  349.             "DisposeSerialRef:  invalid reference"));
  350.         EXECUTE(ArrayDeleteElement(ListOfRefs,ArrayFindElement(ListOfRefs,TheIdentifier)));
  351.         ReleasePtr((char*)TheIdentifier);
  352.     }
  353.  
  354.  
  355. /* find out the closest available baud rate to the one requested.  if the */
  356. /* requested baud rate is supported, it is returned.  if not, then the closest */
  357. /* available rate is returned. */
  358. long                                GetClosestAvailableBaudRate(long RequestedRate,
  359.                                             SerialRefRec* PortIdentifier)
  360.     {
  361.         ERROR(!Initialized,PRERR(ForceAbort,
  362.             "GetClosestAvailableBaudRate:  not initialized"));
  363.         ERROR(ArrayFindElement(ListOfRefs,PortIdentifier) == -1,PRERR(ForceAbort,
  364.             "GetClosestAvailableBaudRate:  invalid reference"));
  365.         ERROR((PortIdentifier->PortIndex < 0) || (PortIdentifier->PortIndex >= 2),
  366.             PRERR(ForceAbort,"GetClosestAvailableBaudRate:  bad port number"));
  367.         CheckPtrExistence(PortIdentifier);
  368.         /* for Macintosh, this doesn't actually depend on the port kind, but on */
  369.         /* some systems it might */
  370.         if (RequestedRate < 300)
  371.             {
  372.                 return 300;
  373.             }
  374.         else if (RequestedRate > 57600)
  375.             {
  376.                 return 57600;
  377.             }
  378.         else
  379.             {
  380.                 return RequestedRate;
  381.             }
  382.     }
  383.  
  384.  
  385. /* find out how much data is waiting to be read */
  386. long                                NumSerialPortBytesWaitingToRead(SerialPortRec* SerialPort)
  387.     {
  388.         long                            NumBytes;
  389.  
  390.         ERROR(!Initialized,PRERR(ForceAbort,
  391.             "NumSerialPortBytesWaitingToRead:  not initialized"));
  392.         CheckPtrExistence(SerialPort);
  393.         SerGetBuf(SerialPort->InputPortRefNum,&NumBytes);
  394.         return NumBytes;
  395.     }
  396.  
  397.  
  398. /* find out how much data is waiting to leave the local buffers */
  399. long                                NumSerialPortBytesWaitingToWrite(SerialPortRec* SerialPort)
  400.     {
  401.         long                            Scan;
  402.         long                            Count;
  403.  
  404.         ERROR(!Initialized,PRERR(ForceAbort,
  405.             "NumSerialPortBytesWaitingToWrite:  not initialized"));
  406.         CheckPtrExistence(SerialPort);
  407.         Count = 0;
  408.         for (Scan = 0; Scan < NUMWRITECELLS; Scan += 1)
  409.             {
  410.                 if (SerialPort->WriteCellArray[Scan].InUseFlag)
  411.                     {
  412.                         /* if the flag gets cleared right before we read it, well, too bad... */
  413.                         Count += SerialPort->WriteCellArray[Scan].NumBytes;
  414.                     }
  415.             }
  416.         return Count;
  417.     }
  418.  
  419.  
  420. /* read some bytes from the port buffer into the specified buffer.  it is an */
  421. /* error to read more bytes than there are waiting. */
  422. void                                ReadSerialPort(SerialPortRec* SerialPort, long NumBytesToRead,
  423.                                             char* Buffer)
  424.     {
  425.         OSErr                            Error;
  426.         long                            OldNumBytes;
  427.  
  428.         ERROR(!Initialized,PRERR(ForceAbort,"ReadSerialPort:  not initialized"));
  429.         ERROR(NumSerialPortBytesWaitingToRead(SerialPort) < NumBytesToRead,
  430.             PRERR(AllowResume,"ReadSerialPort:  reading too many bytes"));
  431.         EXECUTE(OldNumBytes = NumBytesToRead;)
  432.         Error = FSRead(SerialPort->InputPortRefNum,&NumBytesToRead,&(Buffer[0]));
  433.         ERROR(OldNumBytes != NumBytesToRead,PRERR(ForceAbort,
  434.             "ReadSerialPort: [FSRead] read error -- didn't read all the bytes even though they exist"));
  435.     }
  436.  
  437.  
  438. /* submit bytes to be written.  it returns True if successful, or False if */
  439. /* the operation timed out or another error occurred */
  440. MyBoolean                        WriteSerialPort(SerialPortRec* SerialPort, long Length, char* Data)
  441.     {
  442.         long                        StartTime;
  443.         short                        ChosenWriteCell;
  444.         short                        Scan;
  445.  
  446.         ERROR(!Initialized,PRERR(ForceAbort,"WriteSerialPort:  not initialized"));
  447.         CheckPtrExistence(SerialPort);
  448.  
  449.         if (Length == 0)
  450.             {
  451.                 return True;
  452.             }
  453.  
  454.         /* entry point for writing chunks of data */
  455.      ReEntryPoint:
  456.         ChosenWriteCell = -1;
  457.         StartTime = TickCount();
  458.         /* here we loop until we can find a free write cell */
  459.         while (ChosenWriteCell == -1)
  460.             {
  461.                 /* check timeout */
  462.                 if (TickCount() - StartTime > IOTIMEOUT)
  463.                     {
  464.                         /* a timeout occurred because we looped for a while and were unable */
  465.                         /* to write any data. */
  466.                         return False;
  467.                     }
  468.                 /* look for available write cells */
  469.                 for (Scan = 0; (Scan < NUMWRITECELLS) && (ChosenWriteCell == -1); Scan += 1)
  470.                     {
  471.                         if (!SerialPort->WriteCellArray[Scan].InUseFlag)
  472.                             {
  473.                                 ChosenWriteCell = Scan;
  474.                             }
  475.                     }
  476.                 /* relinquish CPU so that user can do something productive while waiting. */
  477.                 if (ChosenWriteCell == -1)
  478.                     {
  479.                         RelinquishCPUCheckCancel();
  480.                     }
  481.             }
  482.  
  483.         /* we found a buffer, so we can set it up for the async. write */
  484.         SerialPort->WriteCellArray[ChosenWriteCell].InUseFlag = True;
  485.         SerialPort->WriteCellArray[ChosenWriteCell].MyPB.ioParam.ioCompletion
  486.             = (ProcPtr)&Callback;
  487.         SerialPort->WriteCellArray[ChosenWriteCell].MyPB.ioParam.ioVRefNum = 0;
  488.         SerialPort->WriteCellArray[ChosenWriteCell].MyPB.ioParam.ioRefNum
  489.             = SerialPort->OutputPortRefNum;
  490.         SerialPort->WriteCellArray[ChosenWriteCell].MyPB.ioParam.ioBuffer
  491.             = SerialPort->WriteCellArray[ChosenWriteCell].Buffer;
  492.         SerialPort->WriteCellArray[ChosenWriteCell].MyPB.ioParam.ioPosMode = fsAtMark;
  493.  
  494.         /* how much to write? */
  495.         if (Length > WRITECELLSIZE)
  496.             {
  497.                 /* if the pending amount of data is larger than the size of a */
  498.                 /* write cell, then we only write the first writecell's worth of data */
  499.                 SerialPort->WriteCellArray[ChosenWriteCell].MyPB.ioParam.ioReqCount
  500.                     = WRITECELLSIZE;
  501.                 SerialPort->WriteCellArray[ChosenWriteCell].NumBytes = WRITECELLSIZE;
  502.                 CopyData(Data,SerialPort->WriteCellArray[ChosenWriteCell].Buffer,WRITECELLSIZE);
  503.                 PBWrite((ParamBlockRec*)&(SerialPort->WriteCellArray[ChosenWriteCell]),
  504.                     True/*async*/);
  505.                 Length -= WRITECELLSIZE;
  506.                 Data += WRITECELLSIZE;
  507.                 goto ReEntryPoint;
  508.             }
  509.          else
  510.             {
  511.                 SerialPort->WriteCellArray[ChosenWriteCell].MyPB.ioParam.ioReqCount = Length;
  512.                 SerialPort->WriteCellArray[ChosenWriteCell].NumBytes = Length;
  513.                 CopyData(Data,SerialPort->WriteCellArray[ChosenWriteCell].Buffer,Length);
  514.                 PBWrite((ParamBlockRec*)&(SerialPort->WriteCellArray[ChosenWriteCell]),
  515.                     True/*async*/);
  516.             }
  517.         if (TickCount() - StartTime > 1)
  518.             {
  519.                 APRINT(("WriteBlock took %l ticks",(long)(TickCount() - StartTime)));
  520.             }
  521.  
  522.         /* all done */
  523.         return True;
  524.     }
  525.  
  526.  
  527. /* the callback routine must NOT be profiled */
  528. #ifdef THINK_C
  529.     #if __option(profile)
  530.         #define ProfilingEnabled (True)
  531.     #else
  532.         #define ProfilingEnabled (False)
  533.     #endif
  534.  
  535.     #pragma options(!profile)
  536. #endif
  537.  
  538. static pascal void    Callback(void)
  539.     {
  540. #ifndef __cplusplus
  541.         register WriteCell*        HiddenParameter;
  542.  
  543.         asm{move.l A0,HiddenParameter}
  544.         HiddenParameter->InUseFlag = 0;
  545.         HiddenParameter->NumBytes = 0;
  546. #else
  547.         /* 00000000: 2F0C               MOVE.L    A4,-(A7) */
  548.         /* 00000002: 2848               MOVEA.L   A0,A4 */
  549.         /* 00000004: 426C 01D0          CLR.W     $01D0(A4) */
  550.         /* 00000008: 42AC 01D2          CLR.L     $01D2(A4) */
  551.         /* 0000000C: 285F               MOVEA.L   (A7)+,A4 */
  552.         asm(0x2f0c,0x2848,0x426c,0x01d0,0x42ac,0x01d2,0x285f);
  553. #endif
  554.     }
  555.  
  556. #ifdef THINK_C
  557.     #if ProfilingEnabled
  558.         #pragma options(profile)
  559.     #endif
  560. #endif
  561.